/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.nodes;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.Image;
import java.awt.datatransfer.Transferable;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import org.openide.TopManager;
import org.openide.util.HelpCtx;
import org.openide.util.datatransfer.*;
import org.openide.util.actions.SystemAction;
/** A basic implementation of a node.
*
* <p>It simplifies creation of the display name, based on a message
* format and the system name. It also simplifies working with icons:
* one need only specify the base name and all icons will be loaded
* when needed. Other common requirements are handled as well.
*
* @author Jaroslav Tulach */
public class AbstractNode extends Node {
/** messages to create a resource identification for each type of
* icon from the base name for the icon.
*/
private static final MessageFormat[] icons = {
// color 16x16
new MessageFormat ("{0}.gif"), // NOI18N
// color 32x32
new MessageFormat ("{0}32.gif"), // NOI18N
// mono 16x16
new MessageFormat ("{0}.gif"), // NOI18N
// mono 32x32
new MessageFormat ("{0}32.gif"), // NOI18N
// opened color 16x16
new MessageFormat ("{0}Open.gif"), // NOI18N
// opened color 32x32
new MessageFormat ("{0}Open32.gif"), // NOI18N
// opened mono 16x16
new MessageFormat ("{0}Open.gif"), // NOI18N
// opened mono 32x32
new MessageFormat ("{0}Open32.gif"), // NOI18N
};
/** To index normal icon from previous array use
* + ICON_BASE.
*/
private static final int ICON_BASE = -1;
/** for indexing opened icons */
private static final int OPENED_ICON_BASE = 3;
/** empty array of paste types */
private static final PasteType[] NO_PASTE_TYPES = {};
/** empty array of new types */
private static final NewType[] NO_NEW_TYPES = {};
/** empty array of property sets */
private static final PropertySet[] NO_PROPERTY_SETS = {};
/** Message format to use for creation of the display name.
* It permits conversion of text from
* {@link #getName} to the one sent to {@link #setDisplayName}. The format can take
* one parameter, <code>{0}</code>, which will be filled by a value from <CODE>getName()</CODE>.
*
* <p>The default format just uses the simple name; subclasses may
* change it, though it will not take effect until the next {@link #setName} call.
*
* <p>Can be set to <CODE>null</CODE>. Then there is no connection between the
* name and display name; they may be independently modified. */
protected MessageFormat displayFormat;
/** default icon base for all nodes */
private static final String DEFAULT_ICON_BASE = "/org/openide/resources/defaultNode"; // NOI18N
/** Resource base for icons, used as parameter to MessageFormat.format, so
* it is stored as array */
private Object[] iconBase = { DEFAULT_ICON_BASE };
/** array of cookies for this node */
private CookieSet cookieSet;
/** set of properties to use */
private Sheet sheet;
/** Actions for the node. They are used only for the pop-up menus
* of this node.
*/
protected SystemAction[] systemActions;
/** default action */
private SystemAction defaultAction;
/** listener for changes in the sheet */
private PropertyChangeListener sheetL = new PropertyChangeListener () {
public void propertyChange (PropertyChangeEvent ev) {
AbstractNode.this.firePropertySetsChange (null, null);
}
};
/** listener for changes in the cookie set */
private ChangeListener cookieL = new ChangeListener () {
public void stateChanged (ChangeEvent ev) {
AbstractNode.this.fireCookieChange ();
}
};
/** Create a new abstract node with a given child set.
* @param children the children to use for this node
*/
public AbstractNode(Children children) {
super (children);
}
/** Clone the node. If the object implements {@link Cloneable},
* that is used; otherwise a {@link FilterNode filter node}
* is created.
*
* @return copy of this node
*/
public Node cloneNode () {
try {
if (this instanceof Cloneable) {
return (Node)clone ();
}
} catch (CloneNotSupportedException ex) {
}
return new FilterNode (this);
}
/** Set the system name. Fires a property change event.
* Also may change the display name according to {@link #displayFormat}.
*
* @param s the new name
*/
public void setName (String s) {
super.setName (s);
MessageFormat mf = displayFormat;
if (mf != null) {
setDisplayName (mf.format (new Object[] { s }));
}
}
/** Change the icon.
* One need only specify the base resource name;
* the real name of the icon is obtained by the applying icon message
* formats.
*
* <p>For example, for the base <code>/resource/MyIcon</code>, the
* following images may be used according to the icon state and
* {@link java.beans.BeanInfo#getIcon presentation type}:
*
* <ul><li><code>/resource/MyIcon.gif</code><li><code>/resource/MyIconOpen.gif</code>
* <li><code>/resource/MyIcon32.gif</code><li><code>/resource/MyIconOpen32.gif</code></ul>
*
* <P>
* This method may be used to dynamically switch between different sets
* of icons for different configurations. If the set is changed,
* an icon property change event is fired.
*
* @param base base resouce name */
public void setIconBase (String base) {
this.iconBase = new Object[] { base };
fireIconChange ();
fireOpenedIconChange ();
}
/** Find an icon for this node. Uses an {@link #setIconBase icon set}.
*
* @param type constants from {@link java.beans.BeanInfo}
*
* @return icon to use to represent the bean
*/
public Image getIcon (int type) {
return findIcon (type, ICON_BASE);
}
/** Finds an icon for this node when opened. This icon should represent the node
* only when it is opened (when it can have children).
*
* @param type as in {@link #getIcon}
* @return icon to use to represent the bean when opened
*/
public Image getOpenedIcon (int type) {
return findIcon (type, OPENED_ICON_BASE);
}
public HelpCtx getHelpCtx () {
return HelpCtx.DEFAULT_HELP;
}
/** module test mode? if so, use repo classloader */
private static Boolean moduleTestIcons = null;
/** Tries to find the right icon for the iconbase.
* @param type type of icon (from BeanInfo constants)
* @param ib base where to scan in the array
*/
private Image findIcon (int type, int ib) {
ClassLoader loader = getClass ().getClassLoader ();
TopManager top;
if (
//getClass () != AbstractNode.class &&
(top = TopManager.getDefault ()) != null
) {
if (moduleTestIcons == null)
moduleTestIcons = Boolean.valueOf (System.getProperty ("netbeans.module.test")); // NOI18N
// if looking for an icon for subclass => use class's classloader
// if looking for AbstractNode => use systemClassLoader () because
// the icon is probably added by some module
loader = moduleTestIcons.booleanValue () ? top.currentClassLoader () : top.systemClassLoader ();
}
String res = icons[type + ib].format (iconBase);
Image im = IconManager.getIcon (res, loader);
if (im != null) return im;
// try the first icon
res = icons[java.beans.BeanInfo.ICON_COLOR_16x16 + ib].format (iconBase);
im = IconManager.getIcon (res, loader);
if (im != null) return im;
if (ib == OPENED_ICON_BASE) {
// try closed icon also
return findIcon (type, ICON_BASE);
}
// if still not found return default icon
return IconManager.getDefaultIcon ();
}
/** Can this node be renamed?
* @return <code>false</code>
*/
public boolean canRename () {
return false;
}
/** Can this node be destroyed?
* @return <CODE>false</CODE>
*/
public boolean canDestroy () {
return false;
}
/** Set the set of properties.
* A listener is attached to the provided sheet
* and any change of the sheet is propagated to the node by
* firing a {@link #PROP_PROPERTY_SETS} change event.
*
* @param s the sheet to use
*/
protected final synchronized void setSheet (Sheet s) {
if (sheet != null) {
sheet.removePropertyChangeListener (sheetL);
}
s.addPropertyChangeListener (sheetL);
sheet = s;
firePropertySetsChange (null, null);
}
/** Initialize a default
* property sheet; commonly overridden. If {@link #getSheet}
* is called and there is not yet a sheet,
* this method is called to allow a subclass
* to specify its properties.
* <P>
* <em>Warning:</em> Do not call <code>getSheet</code> in this method.
* <P>
* The default implementation returns an empty sheet.
*
* @return the sheet with initialized values (never <code>null</code>)
*/
protected Sheet createSheet () {
return new Sheet ();
}
/** Get the current property sheet. If the sheet has been
* previously set by a call to {@link #setSheet}, that sheet
* is returned. Otherwise {@link #createSheet} is called.
*
* @return the sheet (never <code>null</code>)
*/
protected final Sheet getSheet () {
Sheet s = sheet;
if (s != null) return s;
synchronized (this) {
if (sheet != null) return sheet;
// sets empty sheet and adds a listener to it
setSheet (createSheet ());
return sheet;
}
}
/** Get a list of property sets.
*
* @return the property sets for this node
* @see #getSheet
*/
public PropertySet[] getPropertySets () {
Sheet s = getSheet ();
if (s == null) return NO_PROPERTY_SETS;
return s.toArray ();
}
/** Copy this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not copy
*/
public Transferable clipboardCopy () throws IOException {
return NodeTransfer.transferable (this, NodeTransfer.CLIPBOARD_COPY);
}
/** Cut this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not cut
*/
public Transferable clipboardCut () throws IOException {
return NodeTransfer.transferable (this, NodeTransfer.CLIPBOARD_CUT);
}
/**
* This implementation only calls clipboardCopy supposing that
* copy to clipboard and copy by d'n'd are similar.
*
* @return transferable to represent this node during a drag
* @exception IOException when the
* cut cannot be performed
*/
public Transferable drag () throws IOException {
return clipboardCopy ();
}
/** Can this node be copied?
* @return <code>true</code>
*/
public boolean canCopy () {
return true;
}
/** Can this node be cut?
* @return <code>false</code>
*/
public boolean canCut () {
return false;
}
/** Accumulate the paste types that this node can handle
* for a given transferable.
* <P>
* The default implementation simply tests whether the transferable supports
* intelligent pasting via {@link NodeTransfer#findPaste}, and if so, it obtains the paste types
* from the {@link NodeTransfer.Paste transfer data} and inserts them into the set.
* <p>Subclass implementations should typically call super (first or last) so that they
* add to, rather than replace, a superclass's available paste types; especially as the
* default implementation in <code>AbstractNode</code> is generally desirable to retain.
*
* @param t a transferable containing clipboard data
* @param s a list of {@link PasteType}s that will have added to it all types
* valid for this node (ordered as they will be presented to the user)
*/
protected void createPasteTypes (Transferable t, List s) {
NodeTransfer.Paste p = NodeTransfer.findPaste (t);
if (p != null) {
// adds all its types into the set
s.addAll (Arrays.asList (p.types (this)));
}
}
/** Determine which paste operations are allowed when a given transferable is in the clipboard.
* Subclasses should override {@link #createPasteTypes}.
*
* @param t the transferable in the clipboard
* @return array of operations that are allowed
*/
public final PasteType[] getPasteTypes (Transferable t) {
List s = new LinkedList ();
createPasteTypes (t, s);
return (PasteType[])s.toArray (NO_PASTE_TYPES);
}
/** Default implementation that tries to delegate the implementation
* to the createPasteTypes method. Simply calls the method and
* tries to take the first provided argument. Ignores the action
* argument and index.
*
* @param t the transferable
* @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK
* @param index index between children the drop occured at or -1 if not specified
* @return null if the transferable cannot be accepted or the paste type
* to execute when the drop occures
*/
public PasteType getDropType (Transferable t, int action, int index) {
java.util.List s = new LinkedList ();
createPasteTypes (t, s);
return s.isEmpty () ? null : (PasteType)s.get (0);
}
/* List new types that can be created in this node.
* @return new types
*/
public NewType[] getNewTypes () {
return NO_NEW_TYPES;
}
/* Get the default action.
* @return if there is a default action set, then returns it
*/
public SystemAction getDefaultAction () {
return defaultAction;
}
/** Set a default action for the node.
* @param action the new default action
*/
public void setDefaultAction (SystemAction action) {
defaultAction = action;
}
/** Get all actions for the node.
* Initialized with {@link #createActions}, or with the superclass's list.
*
* @return actions for the node
*/
public SystemAction[] getActions () {
if (systemActions == null) {
systemActions = createActions ();
if (systemActions == null) {
systemActions = super.getActions ();
}
}
return systemActions;
}
/** Lazily initialize set of node's actions (overridable).
* The default implementation returns <code>null</code>.
* <p><em>Warning:</em> do not call {@link #getActions} within this method.
* If necessary, call {@link NodeOp#getDefaultActions} to merge in.
* @return array of actions for this node, or <code>null</code> to use the default node actions
*/
protected SystemAction[] createActions () {
return null;
}
/** Does this node have a customizer?
* @return <CODE>false</CODE>
*/
public boolean hasCustomizer () {
return false;
}
/** Get the customizer.
* @return <code>null</code> in the default implementation
*/
public java.awt.Component getCustomizer () {
return null;
}
/** Set the cookie set.
* A listener is attached to the provided cookie set,
* and any change of the sheet is propagated to the node by
* firing {@link #PROP_COOKIE} change events.
*
* @param s the cookie set to use
*/
protected final synchronized void setCookieSet (CookieSet s) {
if (cookieSet != null) {
cookieSet.removeChangeListener (cookieL);
}
s.addChangeListener (cookieL);
cookieSet = s;
fireCookieChange ();
}
/** Get the cookie set.
*
* @return the cookie set created by {@link #setCookieSet}, or an empty set (never <code>null</code>)
*/
public final CookieSet getCookieSet () {
CookieSet s = cookieSet;
if (s != null) return s;
synchronized (this) {
if (cookieSet != null) return cookieSet;
// sets empty sheet and adds a listener to it
setCookieSet (new CookieSet ());
return cookieSet;
}
}
/** Get a cookie from the node.
* Uses the cookie set as determined by {@link #getCookieSet}.
*
* @param type the representation class
* @return the cookie or <code>null</code>
*/
public Node.Cookie getCookie (Class type) {
CookieSet c = cookieSet;
if (c == null) return null;
return c.getCookie (type);
}
/** Get a serializable handle for this node.
* @return a {@link DefaultHandle} in the default implementation
*/
public Handle getHandle () {
return DefaultHandle.createHandle (this);
}
}
/*
* Log
* 31 Gandalf 1.30 2/4/00 Jesse Glick Icons work for test
* modules.
* 30 Gandalf 1.29 1/12/00 Jesse Glick NOI18N
* 29 Gandalf 1.28 11/11/99 Petr Hrebejk Temporary rollback of
* last change (icon lookup)
* 28 Gandalf 1.27 11/4/99 Jaroslav Tulach Different lookup of
* icons.
* 27 Gandalf 1.26 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 26 Gandalf 1.25 9/21/99 Jesse Glick [JavaDoc]
* 25 Gandalf 1.24 8/17/99 Jan Jancura Bug in firing in
* setIconBase
* 24 Gandalf 1.23 6/30/99 Jaroslav Tulach Drag and drop support
* 23 Gandalf 1.22 6/24/99 Jesse Glick Nodes can specify context
* help (not yet retrieved by anything, though).
* 22 Gandalf 1.21 6/9/99 Ian Formanek Fixed resources for
* package change
* 21 Gandalf 1.20 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 20 Gandalf 1.19 5/17/99 Jaroslav Tulach 32x32 icon works
* 19 Gandalf 1.18 5/6/99 Jaroslav Tulach Works also without
* TopManager
* 18 Gandalf 1.17 4/28/99 Jesse Glick [JavaDoc]
* 17 Gandalf 1.16 4/28/99 Jaroslav Tulach XML storage for modules.
* 16 Gandalf 1.15 4/24/99 Ian Formanek Removed debug print
* 15 Gandalf 1.14 4/24/99 Ian Formanek Fixed bug which caused
* HTML, URL and Properties object not to have correct icon
* 14 Gandalf 1.13 3/19/99 Jaroslav Tulach
* 13 Gandalf 1.12 3/18/99 Jesse Glick [JavaDoc]
* 12 Gandalf 1.11 3/18/99 Jaroslav Tulach getIcon searches for
* Icon, IconOpen, Icon32 and IconOpen32
* 11 Gandalf 1.10 3/17/99 Jesse Glick [JavaDoc]
* 10 Gandalf 1.9 3/17/99 Petr Hamernik
* 9 Gandalf 1.8 3/17/99 Petr Hamernik accessibility of actions
* field
* 8 Gandalf 1.7 3/16/99 Jesse Glick [JavaDoc]
* 7 Gandalf 1.6 3/4/99 Jaroslav Tulach Changed comment to
* reflect Dafe and Slavek wishes.
* 6 Gandalf 1.5 2/25/99 Jaroslav Tulach Change of clipboard
* management
* 5 Gandalf 1.4 2/17/99 Ian Formanek Updated icons to point to
* the right package (under ide/resources)
* 4 Gandalf 1.3 1/7/99 Jaroslav Tulach
* 3 Gandalf 1.2 1/7/99 Ian Formanek fixed resource names
* 2 Gandalf 1.1 1/6/99 Ian Formanek Fixed outerclass
* specifiers uncompilable under JDK 1.2
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
*/